#include <crt_util.h>
#include <inttypes.h>

#define decimal_point_char '.'

#define sJSON_ArrayForEach(item, array) for (item = (array != NULL) ? (array)->child : NULL; item != NULL; item = item->next)

#define sJson_min(a, b) ((a < b) ? a : b)

static unsigned char* sjson_strdup(const unsigned char* string)
{
    size_t len = 0;
    unsigned char* copy = NULL;

    if (string == NULL) {
        return NULL;
    }

    len = s_strlen((const char*)string) + sizeof("");
    copy = (unsigned char*)sjson_allocate(len);
    if (copy == NULL) {
        return NULL;
    }

    s_memcpy(copy, string, len);

    return copy;
}

static sjsonptr sjson_new_Item()
{
    sjsonptr node = (sjsonptr)sjson_allocate(sizeof(sjson));
    if (node) {
        s_memset(node, 0x00, sizeof(sjson));
    }
    return node;
}

typedef struct {
    unsigned char* buffer;
    size_t length;
    size_t offset;
    size_t depth;
    int format;
} sjsonprint;

typedef sjsonprint* sjsonprintptr;
typedef sjsonprint* const sjsonprintvptr;

typedef struct {
    const unsigned char* content;
    size_t length;
    size_t offset;
    size_t depth;
} sjsonparse;

typedef sjsonparse* sjsonparseptr;
typedef sjsonparse* const sjsonparsevptr;

/* check if the given size is left to read in a given parse buffer (starting with 1) */
#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))

/* check if the buffer can be accessed at the given index (starting with 0) */
#define can_index(buffer, index)    ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
#define cannot_index(buffer, index) (!can_index(buffer, index))

/* get a pointer to the buffer at the position */
#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)

static int parse_num(sjsonvptr item, sjsonparsevptr input_buffer)
{
    double number = 0;
    unsigned char* after_end = NULL;
    unsigned char number_c_string[128] = {0x00};
    unsigned char decimal_point = decimal_point_char;

    int find_decimal_point = 0;
    size_t i = 0;
    size_t len = 0;

    rc_error(input_buffer != NULL, sjson_false) rc_error(input_buffer->content != NULL, sjson_false)

        len = (sizeof(number_c_string) - 1);

    for (i = 0; (i < len) && can_index(input_buffer, i); i++) {
        switch (buffer_at_offset(input_buffer)[i]) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '+':
        case '-':
        case 'e':
        case 'E':
            number_c_string[i] = buffer_at_offset(input_buffer)[i];
            break;
        case '.': {
            find_decimal_point = 1;
            number_c_string[i] = decimal_point;
        } break;

        default:
            goto loop_end;
        }
    }
loop_end:
    number_c_string[i] = '\0';

    number = strtod((const char*)number_c_string, (char**)&after_end);
    if (number_c_string == after_end) {
        return sjson_false;
    }

    if (find_decimal_point) {
        item->valuedouble = number;
        item->type = sjson_double;
    } else {
        if (number >= INT_MIN && number <= INT_MAX) {
            item->valueint = (int)number;
            item->type = sjson_int;
        } else {
            item->valueint64 = (int64)number;
            item->type = sjson_int64;
        }
    }

    input_buffer->offset += (size_t)(after_end - number_c_string);
    return sjson_true;
}

static unsigned char* ensure(sjsonprintvptr p, size_t needed)
{
    unsigned char* newbuffer = NULL;
    size_t newsize = 0;

    rc_error(p != NULL, NULL) rc_error(p->buffer != NULL, NULL)

        if ((p->length > 0) && (p->offset >= p->length))
    {
        return NULL;
    }

    if (needed > INT_MAX) {
        return NULL;
    }

    needed += p->offset + 1;
    if (needed <= p->length) {
        return p->buffer + p->offset;
    }

    if (needed > (INT_MAX / 2)) {
        if (needed <= INT_MAX) {
            newsize = INT_MAX;
        } else {
            return NULL;
        }
    } else {
        newsize = needed * 2;
    }

    newbuffer = (unsigned char*)sjson_reallocate(p->buffer, newsize);

    if (newbuffer == NULL) {
        sjson_deallocate(p->buffer);
        p->length = 0;
        p->buffer = NULL;
        return NULL;
    }

    p->length = newsize;
    p->buffer = newbuffer;

    return newbuffer + p->offset;
}

static void update_offset(sjsonprintvptr buffer)
{
    const unsigned char* buffer_pointer = NULL;
    rc_return(buffer != NULL);
    rc_return(buffer->buffer != NULL);

    buffer_pointer = buffer->buffer + buffer->offset;

    buffer->offset += strlen((const char*)buffer_pointer);
}
static int print_int64(sjsoncvptr item, sjsonprintvptr output_buffer)
{
    unsigned char* output_pointer = NULL;
    int64 d = item->valueint64;
    int length = 0;
    size_t i = 0;
    unsigned char number_buffer[41] = {0x00};
    if (output_buffer == NULL) {
        return sjson_false;
    }

#if (TARGET_OS == OS_WIN)
    length = snprintf((char*)number_buffer, 41, fmt_i64d, d);
#elif (TARGET_OS == OS_POSIX)
    length = sprintf((char*)number_buffer, "%" PRId64, d);
#elif (TARGET_OS == OS_MACH)
    length = sprintf((char*)number_buffer, "%" PRId64, d);
#elif (TARGET_OS == OS_UNIX)
    length = sprintf((char*)number_buffer, "%" PRId64, d);
#endif

    if (length < 0) {
        return sjson_false;
    }

    if (length > (int)(sizeof(number_buffer) - 1)) {
        return sjson_false;
    }

    output_pointer = ensure(output_buffer, (size_t)length);
    if (output_pointer == NULL) {
        return sjson_false;
    }

    for (i = 0; i < ((size_t)length); i++) {
        output_pointer[i] = number_buffer[i];
    }

    output_pointer[i] = '\0';

    output_buffer->offset += (size_t)length;

    return sjson_true;
}
static int print_int(sjsoncvptr item, sjsonprintvptr output_buffer)
{
    unsigned char* output_pointer = NULL;
    int d = item->valueint;
    int length = 0;
    size_t i = 0;
    unsigned char number_buffer[32] = {0x00};
    if (output_buffer == NULL) {
        return sjson_false;
    }

    length = sprintf((char*)number_buffer, "%d", d);

    if (length < 0) {
        return sjson_false;
    }

    if (length > (int)(sizeof(number_buffer) - 1)) {
        return sjson_false;
    }

    output_pointer = ensure(output_buffer, (size_t)length);
    if (output_pointer == NULL) {
        return sjson_false;
    }

    for (i = 0; i < ((size_t)length); i++) {
        output_pointer[i] = number_buffer[i];
    }

    output_pointer[i] = '\0';

    output_buffer->offset += (size_t)length;

    return sjson_true;
}
static int print_double(sjsoncvptr item, sjsonprintvptr output_buffer)
{
    unsigned char* output_pointer = NULL;
    double d = item->valuedouble;
    int length = 0;
    size_t i = 0;
    unsigned char number_buffer[26] = {0x00};

    unsigned char decimal_point = decimal_point_char;
    double test;

    if (output_buffer == NULL) {
        return sjson_false;
    }

    if ((d * 0) != 0) {
        length = sprintf((char*)number_buffer, "null");
    } else {
        length = sprintf((char*)number_buffer, "%1.15g", d);

        if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) {
            length = sprintf((char*)number_buffer, "%1.17g", d);
        }
    }

    if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) {
        return sjson_false;
    }

    output_pointer = ensure(output_buffer, (size_t)length);
    if (output_pointer == NULL) {
        return sjson_false;
    }

    for (i = 0; i < ((size_t)length); i++) {
        if (number_buffer[i] == decimal_point) {
            output_pointer[i] = '.';
            continue;
        }

        output_pointer[i] = number_buffer[i];
    }
    output_pointer[i] = '\0';

    output_buffer->offset += (size_t)length;

    return sjson_true;
}

static unsigned parse_hex4(const unsigned char* const input)
{
    unsigned int h = 0;
    size_t i = 0;

    for (i = 0; i < 4; i++) {
        /* parse digit */
        if ((input[i] >= '0') && (input[i] <= '9')) {
            h += (unsigned int)input[i] - '0';
        } else if ((input[i] >= 'A') && (input[i] <= 'F')) {
            h += (unsigned int)10 + input[i] - 'A';
        } else if ((input[i] >= 'a') && (input[i] <= 'f')) {
            h += (unsigned int)10 + input[i] - 'a';
        } else /* invalid */
        {
            return 0;
        }

        if (i < 3) {
            /* shift left to make place for the next nibble */
            h = h << 4;
        }
    }

    return h;
}

static unsigned char to_utf8(const unsigned char* const input, const unsigned char* const end, unsigned char** output)
{
    long unsigned int codepoint = 0;
    unsigned int first_code = 0;
    const unsigned char* first_sequence = input;
    unsigned char utf8_length = 0;
    unsigned char utf8_position = 0;
    unsigned char sequence_length = 0;
    unsigned char first_byte_mark = 0;

    if ((end - first_sequence) < 6) {
        goto fail;
    }

    /* get the first utf16 sequence */
    first_code = parse_hex4(first_sequence + 2);

    /* check that the code is valid */
    if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) {
        goto fail;
    }

    /* UTF16 surrogate pair */
    if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) {
        const unsigned char* second_sequence = first_sequence + 6;
        unsigned int second_code = 0;
        sequence_length = 12; /* \uXXXX\uXXXX */

        if ((end - second_sequence) < 6) {
            /* input ends unexpectedly */
            goto fail;
        }

        if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) {
            /* missing second half of the surrogate pair */
            goto fail;
        }

        /* get the second utf16 sequence */
        second_code = parse_hex4(second_sequence + 2);
        /* check that the code is valid */
        if ((second_code < 0xDC00) || (second_code > 0xDFFF)) {
            /* invalid second half of the surrogate pair */
            goto fail;
        }

        /* calculate the unicode codepoint from the surrogate pair */
        codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));
    } else {
        sequence_length = 6; /* \uXXXX */
        codepoint = first_code;
    }

    /* encode as UTF-8
     * takes at maximum 4 bytes to encode:
     * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
    if (codepoint < 0x80) {
        /* normal ascii, encoding 0xxxxxxx */
        utf8_length = 1;
    } else if (codepoint < 0x800) {
        /* two bytes, encoding 110xxxxx 10xxxxxx */
        utf8_length = 2;
        first_byte_mark = 0xC0; /* 11000000 */
    } else if (codepoint < 0x10000) {
        /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */
        utf8_length = 3;
        first_byte_mark = 0xE0; /* 11100000 */
    } else if (codepoint <= 0x10FFFF) {
        /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */
        utf8_length = 4;
        first_byte_mark = 0xF0; /* 11110000 */
    } else {
        /* invalid unicode codepoint */
        goto fail;
    }

    /* encode as utf8 */
    for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) {
        /* 10xxxxxx */
        (*output)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);
        codepoint >>= 6;
    }
    /* encode first byte */
    if (utf8_length > 1) {
        (*output)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);
    } else {
        (*output)[0] = (unsigned char)(codepoint & 0x7F);
    }

    *output += utf8_length;

    return sequence_length;

fail:
    return 0;
}

/* Parse the input text into an unescaped cinput, and populate item. */
static int parse_string(sjsonvptr item, sjsonparsevptr input_buffer)
{
    const unsigned char* input_pointer = buffer_at_offset(input_buffer) + 1;
    const unsigned char* input_end = buffer_at_offset(input_buffer) + 1;
    unsigned char* output_pointer = NULL;
    unsigned char* output = NULL;

    /* not a string */
    if (buffer_at_offset(input_buffer)[0] != '\"') {
        goto fail;
    }

    {
        /* calculate approximate size of the output (overestimate) */
        size_t allocation_length = 0;
        size_t skipped_bytes = 0;
        while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) {
            /* is escape sequence */
            if (input_end[0] == '\\') {
                if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) {
                    /* prevent buffer overflow when last input character is a backslash */
                    goto fail;
                }
                skipped_bytes++;
                input_end++;
            }
            input_end++;
        }
        if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) {
            goto fail; /* string ended unexpectedly */
        }

        /* This is at most how much we need for the output */
        allocation_length = (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
        output = (unsigned char*)sjson_allocate(allocation_length + sizeof(""));
        if (output == NULL) {
            goto fail; /* allocation failure */
        }
    }

    output_pointer = output;
    /* loop through the string literal */
    while (input_pointer < input_end) {
        if (*input_pointer != '\\') {
            *output_pointer++ = *input_pointer++;
        }
        /* escape sequence */
        else {
            unsigned char sequence_length = 2;
            if ((input_end - input_pointer) < 1) {
                goto fail;
            }

            switch (input_pointer[1]) {
            case 'b':
                *output_pointer++ = '\b';
                break;
            case 'f':
                *output_pointer++ = '\f';
                break;
            case 'n':
                *output_pointer++ = '\n';
                break;
            case 'r':
                *output_pointer++ = '\r';
                break;
            case 't':
                *output_pointer++ = '\t';
                break;
            case '\"':
            case '\\':
            case '/':
                *output_pointer++ = input_pointer[1];
                break;

            /* UTF-16 literal */
            case 'u':
                sequence_length = to_utf8(input_pointer, input_end, &output_pointer);
                if (sequence_length == 0) {
                    /* failed to convert UTF16-literal to UTF-8 */
                    goto fail;
                }
                break;

            default:
                goto fail;
            }
            input_pointer += sequence_length;
        }
    }

    /* zero terminate the output */
    *output_pointer = '\0';

    item->type = sjson_string;
    item->valuestring = (char*)output;

    input_buffer->offset = (size_t)(input_end - input_buffer->content);
    input_buffer->offset++;

    return sjson_true;

fail:
    if (output != NULL) {
        sjson_deallocate(output);
    }

    if (input_pointer != NULL) {
        input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
    }

    return sjson_false;
}

static int print_string_ptr(const unsigned char* const input, sjsonprintvptr output_buffer)
{
    const unsigned char* input_pointer = NULL;
    unsigned char* output = NULL;
    unsigned char* output_pointer = NULL;
    size_t output_length = 0;

    /* numbers of additional characters needed for escaping */
    size_t escape_characters = 0;

    if (output_buffer == NULL) {
        return sjson_false;
    }

    /* empty string */
    if (input == NULL) {
        output = ensure(output_buffer, sizeof("\"\""));
        if (output == NULL) {
            return sjson_false;
        }
        s_strncpy((char*)output, "\"\"", s_strlen("\"\""));

        return sjson_true;
    }

    /* set "flag" to 1 if something needs to be escaped */
    for (input_pointer = input; *input_pointer; input_pointer++) {
        switch (*input_pointer) {
        case '\"':
        case '\\':
        case '\b':
        case '\f':
        case '\n':
        case '\r':
        case '\t':
            /* one character escape sequence */
            escape_characters++;
            break;
        default:
            if (*input_pointer < 32) {
                /* UTF-16 escape sequence uXXXX */
                escape_characters += 5;
            }
            break;
        }
    }
    output_length = (size_t)(input_pointer - input) + escape_characters;

    output = ensure(output_buffer, output_length + sizeof("\"\""));
    if (output == NULL) {
        return sjson_false;
    }

    /* no characters have to be escaped */
    if (escape_characters == 0) {
        output[0] = '\"';
        memcpy(output + 1, input, output_length);
        output[output_length + 1] = '\"';
        output[output_length + 2] = '\0';

        return sjson_true;
    }

    output[0] = '\"';
    output_pointer = output + 1;
    /* copy the string */
    for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) {
        if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) {
            /* normal character, copy */
            *output_pointer = *input_pointer;
        } else {
            /* character needs to be escaped */
            *output_pointer++ = '\\';
            switch (*input_pointer) {
            case '\\':
                *output_pointer = '\\';
                break;
            case '\"':
                *output_pointer = '\"';
                break;
            case '\b':
                *output_pointer = 'b';
                break;
            case '\f':
                *output_pointer = 'f';
                break;
            case '\n':
                *output_pointer = 'n';
                break;
            case '\r':
                *output_pointer = 'r';
                break;
            case '\t':
                *output_pointer = 't';
                break;
            default:
                sprintf((char*)output_pointer, "u%04x", *input_pointer);
                output_pointer += 4;
                break;
            }
        }
    }
    output[output_length + 1] = '\"';
    output[output_length + 2] = '\0';

    return sjson_true;
}

static int print_string(sjsoncvptr item, sjsonprintvptr p)
{
    return print_string_ptr((unsigned char*)item->valuestring, p);
}

static int parse_value(sjsonvptr item, sjsonparsevptr input_buffer);
static int print_value(sjsoncvptr item, sjsonprintvptr output_buffer);

static int parse_array(sjsonvptr item, sjsonparsevptr input_buffer);
static int print_array(sjsoncvptr item, sjsonprintvptr output_buffer);

static int parse_object(sjsonvptr item, sjsonparsevptr input_buffer);
static int print_object(sjsoncvptr item, sjsonprintvptr output_buffer);

#define bomchar "\xEF\xBB\xBF"

static sjsonparseptr skip_utf8_bom(sjsonparsevptr buffer)
{
    int bom_on = 0;
    rc_error(buffer != NULL, NULL) rc_error(buffer->content != NULL, NULL) rc_error(buffer->offset == 0, NULL)

        bom_on = (s_strncmp((const char*)buffer_at_offset(buffer), bomchar, 3) == 0);

    if (can_index(buffer, 4) && bom_on) {
        buffer->offset += 3;
    }

    return buffer;
}

static sjsonparseptr skip_character(sjsonparsevptr buffer)
{
    rc_error(buffer != NULL, NULL) rc_error(buffer->content != NULL, NULL)

        while (can_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32))
    {
        buffer->offset++;
    }

    if (buffer->offset == buffer->length) {
        buffer->offset--;
    }

    return buffer;
}

sjsonptr sjson_parse(const char* value, unsigned long len, int fmt)
{
    sjsonparse buffer = {0, 0, 0, 0};
    sjsonptr item = NULL;
    rc_error(value != NULL, NULL)

        buffer.content = (const unsigned char*)value;

    buffer.length = (len == 0 ? s_strlen(value) : len);
    buffer.length += 1;
    buffer.offset = 0;

    item = sjson_new_Item();
    if (item == NULL) {
        goto fail;
    }

    skip_utf8_bom(&buffer);
    skip_character(&buffer);

    if (parse_value(item, &buffer) == sjson_false) {
        goto fail;
    }

    skip_character(&buffer);
    if (buffer.offset >= buffer.length) {
        goto fail;
    }

    if (buffer_at_offset(&buffer)[0] != '\0') {
        goto fail;
    }

    return item;

fail:
    if (item != NULL) {
        sjson_delete(item);
    }

    return NULL;
}

char* sjson_print(sjsoncptr item, unsigned long len, int fmt)
{
    sjsonprint p = {0, 0, 0, 0, 0};

    rc_error(len > 0, NULL) rc_error(sjson_is_invalid(item) != sjson_true, NULL) p.buffer = (unsigned char*)sjson_allocate(len);
    rc_error(p.buffer != NULL, NULL)

        p.length = (size_t)len;
    p.offset = 0;
    p.format = fmt;

    if (print_value(item, &p) == sjson_false) {
        sjson_deallocate(p.buffer);
        return NULL;
    }

    return (char*)p.buffer;
}

static int parse_value(sjsonvptr item, sjsonparsevptr input)
{
    int num_on = 0;
    rc_error(input->content != NULL, sjson_false) rc_error(input != NULL, sjson_false)

        /* string */
        if (can_index(input, 0) && (buffer_at_offset(input)[0] == '\"'))
    {
        return parse_string(item, input);
    }

    /* num */
    num_on = ((buffer_at_offset(input)[0] >= '0') && (buffer_at_offset(input)[0] <= '9'));
    if (can_index(input, 0) && ((buffer_at_offset(input)[0] == '-') || num_on)) {
        return parse_num(item, input);
    }

    /* array */
    if (can_index(input, 0) && (buffer_at_offset(input)[0] == '[')) {
        return parse_array(item, input);
    }

    /* object */
    if (can_index(input, 0) && (buffer_at_offset(input)[0] == '{')) {
        return parse_object(item, input);
    }

    return sjson_false;
}

static int print_value(sjsoncvptr item, sjsonprintvptr output_buffer)
{
    rc_error(output_buffer != NULL, sjson_false) rc_error(item != NULL, sjson_false)

        switch ((item->type) & 0xFF)
    {
    case sjson_int64:
        return print_int64(item, output_buffer);
    case sjson_int:
        return print_int(item, output_buffer);
    case sjson_double:
        return print_double(item, output_buffer);
    case sjson_string:
        return print_string(item, output_buffer);
    case sjson_array:
        return print_array(item, output_buffer);
    case sjson_obj:
        return print_object(item, output_buffer);
    default:
        return sjson_false;
    }
}

static int parse_array(sjsonvptr item, sjsonparsevptr input_buffer)
{
    sjsonptr head = NULL;
    sjsonptr cur_item = NULL;

    if (input_buffer->depth >= sjson_nesting_limit) {
        return sjson_false;
    }
    input_buffer->depth++;

    if (buffer_at_offset(input_buffer)[0] != '[') {
        goto fail;
    }

    input_buffer->offset++;
    skip_character(input_buffer);
    if (can_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) {
        goto success;
    }

    /* check if we skipped to the end of the buffer */
    if (cannot_index(input_buffer, 0)) {
        input_buffer->offset--;
        goto fail;
    }

    /* step back to character in front of the first element */
    input_buffer->offset--;
    /* loop through the comma separated array elements */
    do {
        /* allocate next item */
        sjsonptr new_item = sjson_new_Item();
        if (new_item == NULL) {
            goto fail; /* allocation failure */
        }

        /* attach next item to list */
        if (head == NULL) {
            /* start the linked list */
            cur_item = head = new_item;
        } else {
            /* add to the end and advance */
            cur_item->next = new_item;
            new_item->prev = cur_item;
            cur_item = new_item;
        }

        /* parse next value */
        input_buffer->offset++;
        skip_character(input_buffer);
        if (parse_value(cur_item, input_buffer) == sjson_false) {
            goto fail; /* failed to parse value */
        }
        skip_character(input_buffer);
    } while (can_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));

    if (cannot_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') {
        goto fail; /* expected end of array */
    }

success:
    input_buffer->depth--;

    item->type = sjson_array;
    item->child = head;

    input_buffer->offset++;

    return sjson_true;

fail:
    if (head != NULL) {
        sjson_delete(head);
    }

    return sjson_false;
}

static int print_array(sjsoncvptr item, sjsonprintvptr output_buffer)
{
    unsigned char* output_pointer = NULL;
    size_t length = 0;
    sjsonptr current_element = item->child;

    if (output_buffer == NULL) {
        return sjson_false;
    }

    output_pointer = ensure(output_buffer, 1);
    if (output_pointer == NULL) {
        return sjson_false;
    }

    *output_pointer = '[';
    output_buffer->offset++;
    output_buffer->depth++;

    while (current_element != NULL) {
        if (print_value(current_element, output_buffer) == sjson_false) {
            return sjson_false;
        }
        update_offset(output_buffer);
        if (current_element->next) {
            length = (size_t)(output_buffer->format ? 2 : 1);
            output_pointer = ensure(output_buffer, length + 1);
            if (output_pointer == NULL) {
                return sjson_false;
            }
            *output_pointer++ = ',';
            if (output_buffer->format) {
                *output_pointer++ = ' ';
            }
            *output_pointer = '\0';
            output_buffer->offset += length;
        }
        current_element = current_element->next;
    }

    output_pointer = ensure(output_buffer, 2);
    if (output_pointer == NULL) {
        return sjson_false;
    }
    *output_pointer++ = ']';
    *output_pointer = '\0';
    output_buffer->depth--;

    return sjson_true;
}

static int parse_object(sjsonvptr item, sjsonparsevptr input_buffer)
{
    sjsonptr head = NULL; /* linked list head */
    sjsonptr cur_item = NULL;

    if (input_buffer->depth >= sjson_nesting_limit) {
        return sjson_false;
    }
    input_buffer->depth++;

    if (cannot_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) {
        goto fail;
    }

    input_buffer->offset++;
    skip_character(input_buffer);
    if (can_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) {
        goto success;
    }

    if (cannot_index(input_buffer, 0)) {
        input_buffer->offset--;
        goto fail;
    }

    /* step back to character in front of the first element */
    input_buffer->offset--;
    /* loop through the comma separated array elements */
    do {
        /* allocate next item */
        sjsonptr new_item = sjson_new_Item();
        if (new_item == NULL) {
            goto fail; /* allocation failure */
        }

        /* attach next item to list */
        if (head == NULL) {
            /* start the linked list */
            cur_item = head = new_item;
        } else {
            /* add to the end and advance */
            cur_item->next = new_item;
            new_item->prev = cur_item;
            cur_item = new_item;
        }

        /* parse the name of the child */
        input_buffer->offset++;
        skip_character(input_buffer);

        if (parse_string(cur_item, input_buffer) == sjson_false) {
            goto fail; /* faile to parse name */
        }

        skip_character(input_buffer);

        /* swap valuestring and string, because we parsed the name */
        cur_item->key = cur_item->valuestring;
        cur_item->valuestring = NULL;

        if (cannot_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) {
            goto fail; /* invalid object */
        }

        /* parse the value */
        input_buffer->offset++;
        skip_character(input_buffer);
        if (parse_value(cur_item, input_buffer) == sjson_false) {
            goto fail; /* failed to parse value */
        }
        skip_character(input_buffer);
    } while (can_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));

    if (cannot_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) {
        goto fail; /* expected end of object */
    }

success:
    input_buffer->depth--;

    item->type = sjson_obj;
    item->child = head;

    input_buffer->offset++;
    return sjson_true;

fail:
    if (head != NULL) {
        sjson_delete(head);
    }

    return sjson_false;
}

static int print_object(sjsoncvptr item, sjsonprintvptr output_buffer)
{
    unsigned char* output_pointer = NULL;
    size_t length = 0;
    sjsonptr cur_item = item->child;

    if (output_buffer == NULL) {
        return sjson_false;
    }

    length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */
    output_pointer = ensure(output_buffer, length + 1);
    if (output_pointer == NULL) {
        return sjson_false;
    }

    *output_pointer++ = '{';
    output_buffer->depth++;
    if (output_buffer->format) {
        *output_pointer++ = '\n';
    }
    output_buffer->offset += length;

    while (cur_item) {
        if (output_buffer->format) {
            size_t i;
            output_pointer = ensure(output_buffer, output_buffer->depth);
            if (output_pointer == NULL) {
                return sjson_false;
            }
            for (i = 0; i < output_buffer->depth; i++) {
                *output_pointer++ = '\t';
            }
            output_buffer->offset += output_buffer->depth;
        }

        /* print key */
        if (print_string_ptr((unsigned char*)cur_item->key, output_buffer) == sjson_false) {
            return sjson_false;
        }
        update_offset(output_buffer);

        length = (size_t)(output_buffer->format ? 2 : 1);
        output_pointer = ensure(output_buffer, length);
        if (output_pointer == NULL) {
            return sjson_false;
        }
        *output_pointer++ = ':';
        if (output_buffer->format) {
            *output_pointer++ = '\t';
        }
        output_buffer->offset += length;

        if (print_value(cur_item, output_buffer) == sjson_false) {
            return sjson_false;
        }
        update_offset(output_buffer);

        length = (size_t)((output_buffer->format ? 1 : 0) + (cur_item->next ? 1 : 0));
        output_pointer = ensure(output_buffer, length + 1);
        if (output_pointer == NULL) {
            return sjson_false;
        }
        if (cur_item->next) {
            *output_pointer++ = ',';
        }

        if (output_buffer->format) {
            *output_pointer++ = '\n';
        }
        *output_pointer = '\0';
        output_buffer->offset += length;

        cur_item = cur_item->next;
    }

    output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
    if (output_pointer == NULL) {
        return sjson_false;
    }
    if (output_buffer->format) {
        size_t i;
        for (i = 0; i < (output_buffer->depth - 1); i++) {
            *output_pointer++ = '\t';
        }
    }
    *output_pointer++ = '}';
    *output_pointer = '\0';
    output_buffer->depth--;

    return sjson_true;
}

static sjsonptr get_array_item(sjsoncptr array, size_t index)
{
    sjsonptr current_child = NULL;

    if (array == NULL) {
        return NULL;
    }

    current_child = array->child;
    while ((current_child != NULL) && (index > 0)) {
        index--;
        current_child = current_child->next;
    }

    return current_child;
}

sjsonptr sjson_get_array_item(sjsoncptr array, int index)
{
    sjsonptr item = NULL;
    if (index < 0) {
        return NULL;
    }

    item = get_array_item(array, (size_t)index);
    return item;
}

static void append_object(sjsonptr prev, sjsonptr item)
{
    prev->next = item;
    item->prev = prev;
}

int sjson_delete_item(sjsonptr parent, sjsonvptr item)
{
    rc_error(parent != NULL, sjson_false) rc_error(item != NULL, sjson_false)

        if (item->prev != NULL)
    {
        item->prev->next = item->next;
    }

    if (item->next != NULL) {
        item->next->prev = item->prev;
    }

    if (item == parent->child) {
        parent->child = item->next;
    }

    item->prev = NULL;
    item->next = NULL;

    return sjson_true;
}

int sjson_delete(sjsonptr item)
{
    sjsonptr next = NULL;
    while (item != NULL) {
        next = item->next;

        if (item->child != NULL) {
            sjson_delete(item->child);
        }

        if ((item->valuestring != NULL) && (sjson_is_string(item) == sjson_true)) {
            sjson_deallocate(item->valuestring);
        }

        if (item->key != NULL) {
            sjson_deallocate(item->key);
        }

        sjson_deallocate(item);

        item = next;
    }

    return sjson_true;
}

int sjson_replace_item(sjsonvptr parent, sjsonvptr item, sjsonptr replace)
{
    rc_error(parent != NULL, sjson_false) rc_error(item != NULL, sjson_false) rc_error(replace != NULL, sjson_false)

        replace->next = item->next;
    replace->prev = item->prev;

    if (replace->next != NULL) {
        replace->next->prev = replace;
    }
    if (replace->prev != NULL) {
        replace->prev->next = replace;
    }
    if (parent->child == item) {
        parent->child = replace;
    }

    item->next = NULL;
    item->prev = NULL;

    sjson_delete(item);

    return sjson_true;
}

int sjson_add_array(sjsonptr array, sjsonptr item)
{
    sjsonptr child = NULL;

    rc_error(array != NULL, sjson_false) rc_error(item != NULL, sjson_false)

        child = array->child;

    if (child == NULL) {
        array->child = item;
    } else {
        while (child->next) {
            child = child->next;
        }
        append_object(child, item);
    }

    return sjson_true;
}

int sjson_add_obj(sjsonptr object, const char* key, sjsonptr item)
{
    rc_error(key != NULL, sjson_false) rc_error(item != NULL, sjson_false)

        item->key = (char*)sjson_strdup((const unsigned char*)key);
    return sjson_add_array(object, item);
}

int sjson_insert_array(sjsonptr array, unsigned int index, sjsonptr newitem)
{
    sjsonptr item = NULL;

    rc_error(index > 0, sjson_false)

        item = get_array_item(array, index);

    if (item == NULL) {
        return sjson_add_array(array, newitem);
    }

    newitem->next = item;
    newitem->prev = item->prev;
    item->prev = newitem;

    if (item == array->child) {
        array->child = newitem;
    } else {
        newitem->prev->next = newitem;
    }

    return sjson_true;
}

int sjson_get_array_size(sjsoncptr array)
{
    sjsonptr child = NULL;
    size_t size = 0;

    if (array == NULL) {
        return 0;
    }

    child = array->child;

    while (child != NULL) {
        size++;
        child = child->next;
    }

    return (int)size;
}

int sjson_compare(sjsoncvptr a, sjsoncvptr b)
{
    rc_error(a != NULL, sjson_false) rc_error(b != NULL, sjson_false) rc_error((a->type & 0xFF) == (b->type & 0xFF), sjson_false)

        switch (a->type & 0xFF)
    {
    case sjson_int64:
    case sjson_int:
    case sjson_double:
    case sjson_string:
    case sjson_array:
    case sjson_obj:
        break;
    default:
        return sjson_false;
    }

    if (a == b) {
        return S_SUCCESS;
    }

    switch (a->type & 0xFF) {
    case sjson_int64:
        if (a->valueint64 == b->valueint64) {
            return sjson_true;
        }
        return sjson_false;
    case sjson_int:
        if (a->valueint == b->valueint) {
            return sjson_true;
        }
        return sjson_false;
    case sjson_double:
        if (a->valuedouble == b->valuedouble) {
            return S_SUCCESS;
        }
        return sjson_false;
    case sjson_string:
        if ((a->valuestring == NULL) || (b->valuestring == NULL)) {
            return sjson_false;
        }
        if (strcmp(a->valuestring, b->valuestring) == 0) {
            return sjson_true;
        }
        return sjson_false;

    case sjson_array: {
        sjsonptr a_obj = a->child;
        sjsonptr b_obj = b->child;

        for (; (a_obj != NULL) && (b_obj != NULL);) {
            if (!sjson_compare(a_obj, b_obj)) {
                return sjson_false;
            }

            a_obj = a_obj->next;
            b_obj = b_obj->next;
        }

        if (a_obj != b_obj) {
            return sjson_false;
        }

        return sjson_true;
    }

    case sjson_obj: {
        sjsonptr a_obj = NULL;
        sjsonptr b_obj = NULL;

        sJSON_ArrayForEach(a_obj, a)
        {
            b_obj = sjson_get_obj(b, a_obj->key);

            if (b_obj == NULL) {
                return sjson_false;
            }

            if (!sjson_compare(a_obj, b_obj)) {
                return sjson_false;
            }
        }

        sJSON_ArrayForEach(b_obj, b)
        {
            a_obj = sjson_get_obj(a, b_obj->key);
            if (a_obj == NULL) {
                return sjson_false;
            }

            if (!sjson_compare(b_obj, a_obj)) {
                return sjson_false;
            }
        }

        return sjson_true;
    }

    default:
        return sjson_false;
    }
    return sjson_false;
}

sjsonptr sjson_duplicate(sjsoncptr item)
{
    sjsonptr newitem = NULL;
    sjsonptr child = NULL;
    sjsonptr next = NULL;
    sjsonptr newchild = NULL;

    if (!item) {
        goto fail;
    }

    newitem = sjson_new_Item();
    if (!newitem) {
        goto fail;
    }

    newitem->type = item->type;
    newitem->valueint64 = item->valueint64;
    newitem->valueint = item->valueint;
    newitem->valuedouble = item->valuedouble;

    if (item->valuestring) {
        newitem->valuestring = (char*)sjson_strdup((unsigned char*)item->valuestring);
        if (!newitem->valuestring) {
            goto fail;
        }
    }

    if (item->key) {
        newitem->key = (char*)sjson_strdup((unsigned char*)item->key);
        if (!newitem->key) {
            goto fail;
        }
    }

    child = item->child;

    while (child != NULL) {
        newchild = sjson_duplicate(child);
        if (!newchild) {
            goto fail;
        }
        if (next != NULL) {
            next->next = newchild;
            newchild->prev = next;
            next = newchild;
        } else {
            newitem->child = newchild;
            next = newchild;
        }
        child = child->next;
    }

    return newitem;

fail:
    if (newitem != NULL) {
        sjson_delete(newitem);
    }

    return NULL;
}

sjsonptr sjson_get_obj(sjsoncvptr obj, const char* const key)
{
    sjsonptr item = NULL;

    rc_error(obj != NULL, NULL) rc_error(key != NULL, NULL)

        item = obj->child;

    while ((item != NULL) && (s_strcmp(key, (item->key)) != 0)) {
        item = item->next;
    }

    return item;
}

sjsonptr sjson_create_array(void)
{
    sjsonptr item = sjson_new_Item();
    if (item) {
        item->type = sjson_array;
    }
    return item;
}

sjsonptr sjson_create_obj(void)
{
    sjsonptr item = sjson_new_Item();
    if (item) {
        item->type = sjson_obj;
    }
    return item;
}
sjsonptr sjson_create_int64(int64 value)
{
    sjsonptr item = sjson_new_Item();
    if (item) {
        item->type = sjson_int64;
        item->valueint64 = value;
    }
    return item;
}
sjsonptr sjson_create_int(int value)
{
    sjsonptr item = sjson_new_Item();
    if (item) {
        item->type = sjson_int;
        item->valueint = value;
    }
    return item;
}
sjsonptr sjson_create_double(double value)
{
    sjsonptr item = sjson_new_Item();
    if (item) {
        item->type = sjson_double;
        item->valuedouble = value;
    }
    return item;
}

sjsonptr sjson_create_string(const char* value)
{
    sjsonptr item = sjson_new_Item();
    if (item) {
        item->type = sjson_string;
        item->valuestring = (char*)sjson_strdup((const unsigned char*)value);
        if (!item->valuestring) {
            sjson_delete(item);
            return NULL;
        }
    }

    return item;
}

sjsonptr sjson_create_int64_array(const int64* values, unsigned int count)
{
    size_t i = 0;
    sjsonptr n = NULL;
    sjsonptr p = NULL;
    sjsonptr a = NULL;

    rc_error(count > 0, NULL);
    rc_error(values != NULL, NULL);

    a = sjson_create_array();
    for (i = 0; a && (i < count); i++) {
        n = sjson_create_int64(values[i]);
        if (!n) {
            sjson_delete(a);
            return NULL;
        }
        if (!i) {
            a->child = n;
        } else {
            append_object(p, n);
        }
        p = n;
    }

    return a;
}

sjsonptr sjson_create_int_array(const int* values, unsigned int count)
{
    size_t i = 0;
    sjsonptr n = NULL;
    sjsonptr p = NULL;
    sjsonptr a = NULL;

    rc_error(count > 0, NULL) rc_error(values != NULL, NULL)

        a = sjson_create_array();
    for (i = 0; a && (i < count); i++) {
        n = sjson_create_int(values[i]);
        if (!n) {
            sjson_delete(a);
            return NULL;
        }
        if (!i) {
            a->child = n;
        } else {
            append_object(p, n);
        }
        p = n;
    }

    return a;
}

sjsonptr sjson_create_double_array(const double* values, unsigned int count)
{
    size_t i = 0;
    sjsonptr n = NULL;
    sjsonptr p = NULL;
    sjsonptr a = NULL;

    rc_error(count > 0, NULL) rc_error(values != NULL, NULL)

        a = sjson_create_array();

    for (i = 0; a && (i < count); i++) {
        n = sjson_create_double(values[i]);
        if (!n) {
            sjson_delete(a);
            return NULL;
        }
        if (!i) {
            a->child = n;
        } else {
            append_object(p, n);
        }
        p = n;
    }

    return a;
}

sjsonptr sjson_create_string_array(const char** values, unsigned int count)
{
    size_t i = 0;
    sjsonptr n = NULL;
    sjsonptr p = NULL;
    sjsonptr a = NULL;

    rc_error(count > 0, NULL) rc_error(values != NULL, NULL)

        a = sjson_create_array();

    for (i = 0; a && (i < count); i++) {
        n = sjson_create_string(values[i]);
        if (!n) {
            sjson_delete(a);
            return NULL;
        }
        if (!i) {
            a->child = n;
        } else {
            append_object(p, n);
        }
        p = n;
    }

    return a;
}

void* sjson_allocate(size_t size)
{
    return heap_malloc(size);
}
void* sjson_reallocate(void* addr, size_t size)
{
    return heap_realloc(addr, size);
}
void sjson_deallocate(void* obj)
{
    heap_free(obj);
}
void sjson_format(char* json)
{
    unsigned char* into = (unsigned char*)json;

    if (json == NULL) {
        return;
    }

    while (*json) {
        if (*json == ' ') {
            json++;
        } else if (*json == '\t') {
            json++;
        } else if (*json == '\r') {
            json++;
        } else if (*json == '\n') {
            json++;
        } else if ((*json == '/') && (json[1] == '/')) {
            while (*json && (*json != '\n')) {
                json++;
            }
        } else if ((*json == '/') && (json[1] == '*')) {
            while (*json && !((*json == '*') && (json[1] == '/'))) {
                json++;
            }
            json += 2;
        } else if (*json == '\"') {
            *into++ = (unsigned char)*json++;
            while (*json && (*json != '\"')) {
                if (*json == '\\') {
                    *into++ = (unsigned char)*json++;
                }
                *into++ = (unsigned char)*json++;
            }
            *into++ = (unsigned char)*json++;
        } else {
            *into++ = (unsigned char)*json++;
        }
    }
    *into = '\0';
}
